/*

*/

// return true if point(x,y) lies inside a box(x,y,width,height)   
static boolean pintersects(int px, int py, int bx, int by, int bw, int bh) {
  return ((px >= bx && px < (bx + bw)) && (py >= by && py < (by + bh)));
}


interface AWidgetListener {
  public void valueChanged(AWidget widget);
}

// abstract class
abstract class AWidget {
  
  public void update() { }
  public void mousePress() {}
  public void mouseRelease() {}

  public void draw() {
    // do bugger all
  }  
}


class AListBox extends AWidget {
  
  // TODO this lot should be in a component class  
  int x;
  int y;
  int height;
  int width;
  PFont font;
 
  // list state 
  ArrayList items;
  int firstRow;
  int curRow;

  public AListBox(int x, int y, int width, int height, PFont font) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.font = font;
    this.items = new ArrayList();
    this.firstRow = 0;
    this.curRow = -1;
  }
  
  public void addItem(Object object) {
    this.items.add(object);
  }
  
  public void addItems(Collection items) {
    Iterator iter = items.iterator();
    while (iter.hasNext()) {
      addItem(iter.next());  
    }
  }

  public int getRowHeight() {
    // hack need to be calculated based on current font  
    return 15;
  }
  
  // calc displayable number of rows 
  private int getNumRows() {
    // calculate list row
    int rowHeight = getRowHeight();
    int numRows = this.height / rowHeight;
    if  (numRows > items.size()) {
      numRows = items.size();
    }
    return numRows;
  }
    
  public void draw() {
    
    // calculate list row
    int rowHeight = getRowHeight();
    int numRows = getNumRows();
    
    // should we always blank display area - flicker/slow ?
    fill(255,255,255);
    rect(x,y,width,height);
    
    textFont(font);
    textSize(10);
    textAlign(LEFT);
    
    // draw actual rows
    for (int i=0; i < numRows; i++) {
      String data = items.get(i).toString();
      int rowPos = y + rowHeight*i;
      println("row " + i + " pos " + rowPos + "=" + data);
      if (curRow == i) {
        println("highlight row " + i);
        // blue
        fill(0,0,255);
        rect(x,rowPos, this.width, rowHeight);  
      }
      // black
      fill(0,0,0);
      //rect(x,rowPos, this.width, rowHeight);    
      text(data, x, rowPos, this.width, rowHeight); 
    }
  }
  
  // this method should be a static method in a class
  public boolean intersect(int xPos, int yPos, int x, int y, int width, int height) {
    return ((xPos >= x && xPos <= (x + width)) && 
        (yPos >= y && yPos <= (y + height)));
  }
   
  // calc list row at x,y pos (return -1 if none)
  public int getRowAt(int xPos, int yPos) {
    // calculate list row
    int rowHeight = getRowHeight();
    int numRows = getNumRows();
    
    // find intersecting row
    for (int i=0; i < numRows; i++) {
      int rowPos = y + rowHeight*i;
      if (intersect(xPos, yPos, x,rowPos, width, rowHeight)) {
        // found row
        println("intersects " + i);
        return i;
      }
    }

    // no match 
    return -1;
  } 
  public boolean update(int mouseX, int mouseY) {
    int row = getRowAt(mouseX, mouseY);
    if (row != -1) {
      curRow = row;
    }
    return row != -1;
  }
}


public class AScrollBar extends AWidget {
  
  // on screen co-ords 
  private int x;
  private int y;
  private int sheight;
  private int swidth;

  // vert=true,horiz=false
  boolean vert;
  
  // value range  
  private int minVal;
  private int maxVal;
  private int value;
  
  // thumb dimensions
  private int thumbDim;
  private int pageSize;

  // mouse
  private boolean over;
  private boolean locked;
  private boolean focus;

  private int changing;
  private AWidgetListener listener;
  
  public AScrollBar(boolean vert) {
    this.vert = vert;
  }
  
  private void init() {
    int numPages = 0; 
    int range;
    // hack: pagesize should always > 0
    if (pageSize == 0) {
      pageSize = Integer.MAX_VALUE;
    } 
    range = maxVal - minVal;
    numPages = range / pageSize;
    if (numPages == 0) {
      numPages = 1;
    }
    thumbDim = (vert ? sheight : swidth)  / numPages;
    if ( (range % pageSize) > 0) {
      // not perfect number of pages
      numPages++;
      // get diff between newer (smaller) pagewidth and previous size and add
      thumbDim += (((vert ? sheight : swidth) / numPages) - thumbDim) / 2;
    }
    // ensure a sane size
    thumbDim = max(4, thumbDim);
  }
 

  public void setBounds(int x, int y, int swidth, int sheight) {
    this.x = x;
    this.y = y;
    this.swidth = swidth;
    this.sheight= sheight;  
    init();
  }
  
  public void setValues(int minVal, int maxVal, int value, int pageSize) {
    this.minVal = minVal;
    this.maxVal = maxVal;
    this.pageSize = pageSize;
    init();
    setValue(value);
    println("minVal="+minVal+" maxVal="+maxVal+" value="+value+" pageSize="+pageSize);
  }
  
  public void setValue(int value) {
    changing++;
    value = max(minVal,value);
    value = min(maxVal,value);
    this.value = value;
    changing--;
  }
  
  public void setListener(AWidgetListener listener) {
    this.listener = listener;   
  }
  
  public void draw() {
    noStroke();
    // draw bar
    fill(220);
    rect(x,y,swidth,sheight);
    // draw thumb
    if ((over || locked) && focus) {
      fill(150);  
    }
    else {
      fill(180);
    }
    if (vert) {
      rect(x, map(value,minVal,maxVal,y,y+sheight-thumbDim), swidth, thumbDim);
    }
    else {
      rect(map(value,minVal,maxVal,x,x+swidth-thumbDim), y, thumbDim, sheight);
    }
  }
  
  // does point(x,y) intersect us
  public boolean intersects(int x, int y) {
    return pintersects(x,y, this.x,this.y, this.swidth, this.sheight);
  }
  
  public void update() {
    if (intersects(mouseX, mouseY)) {
      over = true;
    }
    else {
      over = false;
    }
    if (locked) {
       // assume mouse pos is at centre of thumb
      float tmp;
      if (vert) {
        tmp = constrain(mouseY-thumbDim/2, y, y+sheight-thumbDim);
        tmp = map(tmp, y, y+sheight-thumbDim, minVal, maxVal);
      }
      else {
        tmp = constrain(mouseX-thumbDim/2, x, x+swidth-thumbDim);
        tmp = map(tmp, x, x+swidth-thumbDim, minVal, maxVal);
      }
      setValue(round(tmp));
    }
  }
  
  public void mousePress() {
    if (focus){
      if (over) {
        locked = true;      
      }
      else {
        locked = false;  
      }
    }
  }
  
  public void setFocus(boolean a) {
    focus = a;
  }
  
  public void mouseRelease() {
    if (locked) {
      locked = false;  
      if (listener != null) {
        listener.valueChanged(this);  
      }
    }
  }

  public int getValue() {
    return value;
  }
} 


public class ASlideBar extends AWidget {

  private int x;
  private int y;
  private int swidth;
  private int sheight;
  
  private int minVal;
  private int maxVal;
  private int value;
  
  private boolean over;
  private boolean locked;
  private boolean  vert;
  private boolean focus;
  
  private AWidgetListener listener;
  
  public ASlideBar(boolean vert) {
    this.vert = vert;
  }
  
  public void setBounds(int x, int y, int swidth, int sheight) {
    this.x = x;
    this.y = y;
    this.swidth = swidth;
    this.sheight = sheight;
  }

  public void setValues(int minVal, int maxVal, int value) {
    this.minVal = minVal;
    this.maxVal = maxVal;
    setValue(value);
    println("minVal="+minVal+" maxVal="+maxVal+" value="+value);
  } 
  
  public void setValue(int value) {
    value = max(value, minVal);
    value = min(value, maxVal);  
    this.value = value;
  }
 
  public int getValue() {
    return value;
  }
  
  public void setListener(AWidgetListener listener) {
    this.listener = listener;   
  }
  
  public void update() {
    if (intersects(mouseX, mouseY)) {
      over = true;
    }
    else {
      over = false;
    }    
    if (locked) {
      float tmp;
      if (vert) {
        tmp = constrain(mouseY, y, y+sheight);
        tmp = map(tmp, y, y+sheight, minVal, maxVal);
      }  
      else {
        tmp = constrain(mouseX, x, x+swidth);
        tmp = map(tmp, x, x+swidth, minVal, maxVal);
      }
      setValue(round(tmp));
    }
  }
  
  public void draw() {
    // draw slider box
    fill(220);
    rect(x,y,swidth,sheight);
    // draw slider level
    if ((over || locked) && focus) {
      fill(150);  
    }
    else {
      fill(180);
    }
    float tmp;
    if (vert) {
      tmp =map(value,minVal,maxVal,0,sheight); 
      rect(x, y, swidth, round(tmp));
    }
    else {
      tmp =map(value,minVal,maxVal,0,swidth); 
      rect(x, y,round(tmp),sheight);
    }
  }
  
  public boolean intersects(int x, int y) {
    return pintersects(x,y, this.x,this.y, this.swidth, this.sheight);
  }
  
  public void mousePress() {
    if (focus){
    if (over) {
      locked = true;      
    }
    else {
      locked = false;  
    }
  }
  }
  
  public void setFocus(boolean a){
    focus = a;
  }
  
  public void mouseRelease() {
    if (locked) {
      locked = false;  
      if (listener != null) {
        listener.valueChanged(this);  
      }
    }
  }  
}


public class AButton extends AWidget {
  private int x;
  private int y;
  private int bwidth;
  private int bheight;
  String btext;
  color baseColor; 
  color pressColor; 
  color textColor;
  boolean over; 
  boolean locked;
  boolean focus;
  private AWidgetListener listener;

  public AButton(int x, int y, int bwidth, int bheight, String btext) {
    this.x = x;
    this.y = y;
    this.bwidth = bwidth;
    this.bheight = bheight;
    this.btext = btext;
    this.over = false;
    this.locked = false;
    this.baseColor = color(#f2f2f2);
    this.pressColor = color(50);
    this.textColor = color(100);
  }
  
  public boolean intersects(int x, int y) {
    return pintersects(x,y, this.x,this.y, this.bwidth, this.bheight);
  }

  public void setListener(AWidgetListener listener) {
    this.listener = listener;   
  }
  
  public void setFocus(boolean a) {
    focus = a;
  }
 
  // Updates the over field every frame
  void update() {
    if (intersects(mouseX,mouseY)) {
      over = true;
    } 
    else {
      over = false;
    }
  }

  public void mousePress() {
    if (focus){
    if (over) {
      locked = true;      
    }
    else {
      locked = false;  
    }
  }
  }
  
  public void mouseRelease() {
    if (locked) {
      locked = false;
      if (over) {  
        if (listener != null) {
          listener.valueChanged(this);  
        }
      }
    }
  }

  public void draw() {
    if (locked == true) {
      fill(pressColor);
    } 
    else {
      fill(baseColor);
    }
    if (over && focus) {
      stroke(0); 
    }
    else {
      stroke(100);  
    }    
    rect(x, y, bwidth, bheight);
    fill(100);
    textFont(defaultFont);
    textAlign(CENTER,CENTER);
    text(btext,x,y,bwidth,bheight);
  }  

}


public class ACheckBox extends AWidget {
  
  private int x;
  private int y;
  private int csize;
  private color baseColor;
  private boolean checked;
  private boolean over;
  private boolean locked;
  private boolean focus;
  private String label;
  private color labelColor;
  private AWidgetListener listener;

  public ACheckBox(int x, int y, int csize, String label) {
    this.x = x;
    this.y = y;
    this.csize = csize;
    this.label = label;
    this.labelColor = color(0);
    this.baseColor = color(#f2f2f2);
    this.checked = false;
  }

  public void setListener(AWidgetListener listener) {
    this.listener = listener;   
  }
  public void setFocus(boolean a) {
    focus = a;
  }
  
  public boolean getChecked() {
    return checked;  
  }
  
  public void setChecked(boolean checked) {
    this.checked = checked;
  }
  
  public void draw() {
    fill(baseColor);
    if (over && focus) {
      stroke(0); 
    }
    else {
      stroke(100);  
    }
    rect(x,y, csize, csize);
    if (checked) {
      stroke(50);
      line(x,y, x+csize-1,y+csize-1);
      line(x+csize-1,y, x,y+csize-1);
    }
    fill(100);
    textFont(defaultFont);
    textAlign(LEFT, TOP);
    text(label, x+csize+5,y+2);
  }

  public boolean intersects(int x, int y) {
    return pintersects(x,y, this.x,this.y, this.csize, this.csize);
  }

  public void update() {
    if (intersects(mouseX,mouseY)) {
      over = true;
    } 
    else {
      over = false;
    }
  }
  

  public void mousePress() {
   if (focus){
     if (over) {
      locked = true;      
    }
    else {
      locked = false;  
    }
   }
  }
  
  public void mouseRelease() {
    if (locked) {
      locked = false;
      if (over) {
        checked = !checked;
        if (listener != null) {
          listener.valueChanged(this);  
        }
      }
    }
  }
}


public class ASpinner extends AWidget implements AWidgetListener  {
  
  private int x;
  private int y;
  private int awidth;
  private int aheight;
  private String label;
  
  private AButton upButton;
  private AButton downButton; 
  
  private int maxVal;
  private int minVal;
  private int value;
  
  private color textColor;
  private AWidgetListener listener;  

  public ASpinner (int x, int y, int awidth, int aheight, String label) {
    this.x = x;
    this.y = y;
    this.awidth = awidth;
    this.aheight = aheight;
    this.label = label;
    this.upButton   = new AButton(x+awidth-aheight, y, aheight, aheight/2 , "+");
    this.downButton = new AButton(x+awidth-aheight, y+aheight/2, aheight, aheight/2,  "-");
    upButton.setListener(this);
    downButton.setListener(this);
  }

  public void valueChanged(AWidget widget) {
    if (widget == upButton) {
      setValue(value+1);  
    }
    else if (widget == downButton) {
      setValue(value-1);  
    }
    else {
      println("???"+widget);  
    }
  }

  public void setListener(AWidgetListener listener) {
    this.listener = listener;   
  }
    
  public void setValues(int minVal, int maxVal, int value) {
    this.maxVal = maxVal;
    this.minVal = minVal;
    setValue(value);
  }
  
  public void setValue(int value) {
    value = max(minVal, value);
    value = min(maxVal, value);
    if (value != this.value) {
      this.value = value;
      if (listener != null) {
        listener.valueChanged(this);  
      }
    }
  }
  
  public int getValue() {
    return value;
  }
  
  public void update() {
    upButton.update();
    downButton.update();
  }
  
  public void draw() {
    fill(100);
    textFont(defaultFont);
    textAlign(RIGHT, TOP);
    text(label, x-5,y+2);
    fill(#f2f2f2);
    stroke(100);
    rect(x,y, awidth-aheight, aheight);
    fill(100);
    textAlign(RIGHT, CENTER);
    text(""+value,x,y,awidth-aheight, aheight); 
    upButton.draw();
    downButton.draw();
  }
  
  public void setFocus(boolean a) {
    upButton.setFocus(a);
    downButton.setFocus(a);
  }
  
  public void mousePress() {
    upButton.mousePress();
    downButton.mousePress();
  }
  
  public void mouseRelease() {
    upButton.mouseRelease();
    downButton.mouseRelease();  
  }
}

public class ATextBox extends AWidget {

  private int x;
  private int y;
  private int awidth;
  private int aheight;
  private String label;
  private String value;
  
  public ATextBox (int x, int y, int awidth, int aheight, String label) {
    this.x  = x;
    this.y = y;
    this.awidth = awidth;
    this.aheight = aheight;
    this.label = label;
    this.value = "";
  }
  
  public void setValue(String value) {
    this.value = value;
  }
  
  public String getValue(String value) {
    return value;  
  }
  
  public void draw() {
    fill(0);
    textFont(defaultFont);
    textAlign(RIGHT, TOP);
    text(label, x-5,y+2);
    fill(128);
    noStroke();
    rect(x,y, awidth-aheight, aheight);
    fill(255);
    textAlign(CENTER, CENTER);
    text(value,x,y,awidth-aheight, aheight);
  }
  
  
}

